package genetic;

import genetic.q3.ReadFile;
import genetic.q3.CliquePhenotypeQ3;

import java.io.File;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

public class GeneticAlgorithmQ3 {

	List<Genotype> population;
	int populationSize;
	Phenotype prototype;
	double bestFitness = 0;
	Genotype best = null;
	ReadFile rf = null;

	public GeneticAlgorithmQ3(Phenotype prototype, ReadFile rf){
		this.population = new LinkedList<Genotype>();
		this.prototype = prototype;
		this.rf = rf;
	}

	public void init(int populationSize){
		this.populationSize = populationSize;
		population.clear();
		for (int i = 0; i < populationSize; i++) {
			population.add(prototype.createRandomInstance());
		}
	}

	public Genotype runAlgorithm(int numGenerations, double Pc, double Pm, boolean matingPoolRouletteWheel, String resultFile){
		PrintWriter out = openFile(resultFile);

		double[] initStats = getStatistics();
		System.out.println("Initial population: Best - " + initStats[0] + ", Average - " + initStats[1]);
		out.println(0 + "\t" + initStats[0] + "\t" + initStats[1]);

		for (int generation=1; generation<=numGenerations; generation++){
			population = prototype.getMatingPool(population, populationSize);
			List<Genotype> newPopulation = new LinkedList<Genotype>();
			int len = population.get(0).getBitString().length;
			int ones = 0;
			for (int i=0; i<populationSize; i++){	//breed
				Genotype p = population.get(i).clone();
//				System.out.println("generation "+generation+" p "+i+"\t"+p);
				if (Math.random()<Pc){
					ones = 0;
					//step 1 - relax
					for (int j=0; j<len; j++){	//breed
						double rand = Math.random();
//						System.out.println(i +" rand is "+ rand + "\tones is " + ones);
						if (rand<Pm){		//create offsprings
							p.getBitString()[j] = true;
							ones++;
						}
					}
					//step 2 - repair
					double rand = Math.random()*ones;
//					System.out.println("here rand is "+ rand + "\tones is " + ones);
					for (int j=0; j<len; j++){	//breed
						if(p.getBitString()[j]){
							if (ones-rand<=0){
								for(int k=j+1; k<len; k++){
									if(p.getBitString()[k]){
										rand = Math.random();
										if(rand < 0.5){
											p.getBitString()[k]= false;
										}
										else{
											for(int f=k+1; f<len; f++){
												if(p.getBitString()[f]){
													if(!rf.matrix[k][f]){
														p.getBitString()[f]=false;
													}
												}
											}
											for(int f=0; f<k; f++){
												if(p.getBitString()[f]){
													if(!rf.matrix[f][k]){
														p.getBitString()[f]=false;
													}
												}
											}
										}
									}
								}
								for(int k=j-1; k>=0; k--){
									if(p.getBitString()[k]){
										rand = Math.random();
										if(rand < 0.5)
											p.getBitString()[k]= false;
										else{
											for(int f=k-1; f>=0; f--){
												if(p.getBitString()[f]){
													if(!rf.matrix[f][k]){
														p.getBitString()[f]=false;
													}
												}
											}
										}
									}
								}
							}else ones--;
						}
					}


					ones = 0;
					for (int j=0; j<len; j++){	//breed
						if(p.getBitString()[j]){
							ones++;
						}
					}
					rand = Math.random()*ones;
					for (int j=0; j<len; j++){	//breed
						if(p.getBitString()[j]){
							if (ones-rand<=0){
								for(int k=j+1; k<len; k++){
									if(!p.getBitString()[k]){
										boolean flag=true;
										for (int f=0; f<len && flag; f++){	//breed
											if(p.getBitString()[f]){
												if(!rf.matrix[f][k]){
													flag=false;
												}
											}
										}
										p.getBitString()[k] = flag;
									}
								}
								for(int k=0; k<j; k++){
									if(!p.getBitString()[k]){
										boolean flag=true;
										for (int f=0; f<len && flag; f++){	//breed
											if(p.getBitString()[f]){
												if(!rf.matrix[f][k]){
													flag=false;
												}
											}
										}
										p.getBitString()[k] = flag;
									}
								}
							}else ones--;
						}
					}
				}

				newPopulation.add(prototype.mutate(p, Pm));
			}
			population = newPopulation;
			double[] stats = getStatistics();
			System.out.println("Generation "+generation+": Best - " + stats[0] + ", Average - " + stats[1] + " , Best so far: " + best);
			out.println(generation + "\t" + stats[0] + "\t" + stats[1]);
		}

		System.out.println("Best genotype on final generation: " + best + " with fitness - " + bestFitness);

		out.close();

		return best;
	}

	private double[] getStatistics(){
		double maxFitness = 0;
		double averageFitness = 0;
		Genotype genBest = null;
		for (Genotype g: population){
			double fitness = prototype.fitness(g);
			if(maxFitness < fitness){
				maxFitness = fitness;
				genBest = g;
			}
			averageFitness = averageFitness + fitness;
		}
		averageFitness = averageFitness/(double)population.size();

		if (bestFitness < maxFitness) {
			bestFitness = maxFitness;
			best = genBest;
			System.out.println(bestFitness + " , " + best);
		}
		return new double[]{ maxFitness , averageFitness};
	}

	private PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

	public static void main(String[] args) {

		int run = 3;

		if (run == 3){
			ReadFile rf = new ReadFile();
//			rf.openFile("c-fat500-1.txt");
			rf.openFile("hamming6-2.txt");
//			rf.openFile("p_hat500-1.txt");
			System.out.println("size "+rf.size);
			System.out.println("max "+rf.max);
			GeneticAlgorithmQ3 b = new GeneticAlgorithmQ3(new CliquePhenotypeQ3(rf.size, rf), rf);
			b.init(100);
			b.runAlgorithm(1000, 0.7, 0.3, false, "500q3c-fat500-1.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3c-fat500-2.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3c-fat500-3.txt");
			rf.openFile("hamming6-2.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3hamming6-1.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3hamming6-2.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3hamming6-3.txt");
			rf.openFile("p_hat500-1.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3p_hat500-1.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3p_hat500-2.txt");
			b.init(100);
			b.runAlgorithm(500, 0, 0.001, false, "500q3p_hat500-3.txt");
		}
	}
}